﻿namespace Microsoft.VisualStudio.TestTools.UnitTesting
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// Collects the attribute information to build the assembly with.
    /// </summary>
    internal class AssemblyBuilderAttributeCollector
    {
        private readonly List<AssemblyBuilderCodeAttribute> codeAttributes = new List<AssemblyBuilderCodeAttribute>();
        private readonly List<AssemblyBuilderFilesAttribute> fileAttributes = new List<AssemblyBuilderFilesAttribute>();
        private readonly List<AssemblyBuilderReferencesAttribute> referenceAttributes = new List<AssemblyBuilderReferencesAttribute>();
        private readonly List<AssemblyBuilderResourceAttribute> resourceAttributes = new List<AssemblyBuilderResourceAttribute>();

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyBuilderAttributeCollector"/> class.
        /// </summary>
        /// <param name="type">The type.</param>
        public AssemblyBuilderAttributeCollector(Type type)
        {
            // Get the information from the type and all appropriate methods.
            this.CollectInformation(type);
            foreach (var method in GetMethodsToProcess(type))
            {
                this.CollectInformation(method);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyBuilderAttributeCollector"/> class.
        /// </summary>
        /// <param name="method">The method.</param>
        public AssemblyBuilderAttributeCollector(MethodBase method)
        {
            // Get information from the type and just this method.
            this.CollectInformation(method.DeclaringType);
            this.CollectInformation(method);
        }
        #endregion Constructors

        #region Public Properties
        /// <summary>
        /// Gets the code attributes.
        /// </summary>
        /// <value>The code attributes.</value>
        public IEnumerable<AssemblyBuilderCodeAttribute> CodeAttributes
        {
            get { return this.codeAttributes; }
        }

        /// <summary>
        /// Gets the file attributes.
        /// </summary>
        /// <value>The file attributes.</value>
        public IEnumerable<AssemblyBuilderFilesAttribute> FileAttributes
        {
            get { return this.fileAttributes; }
        }

        /// <summary>
        /// Gets the reference attributes.
        /// </summary>
        /// <value>The reference attributes.</value>
        public IEnumerable<AssemblyBuilderReferencesAttribute> ReferenceAttributes
        {
            get { return this.referenceAttributes; }
        }

        /// <summary>
        /// Gets the resource attributes.
        /// </summary>
        /// <value>The resource attributes.</value>
        public IEnumerable<AssemblyBuilderResourceAttribute> ResourceAttributes
        {
            get { return this.resourceAttributes; }
        }
        #endregion Public Properties

        #region Public Methods
        public static IEnumerable<AssemblyBuilderCodeAttribute> GetCodeAttributes(MemberInfo member)
        {
            return Attribute.GetCustomAttributes(member, typeof(AssemblyBuilderCodeAttribute))
                .Cast<AssemblyBuilderCodeAttribute>();
        }

        public static AssemblyBuilderFilesAttribute GetFilesAttribute(MemberInfo member)
        {
            return (AssemblyBuilderFilesAttribute)Attribute.GetCustomAttribute(member, typeof(AssemblyBuilderFilesAttribute));
        }

        public static AssemblyBuilderReferencesAttribute GetReferencesAttribute(MemberInfo member)
        {
            return (AssemblyBuilderReferencesAttribute)Attribute.GetCustomAttribute(member, typeof(AssemblyBuilderReferencesAttribute));
        }

        public static IEnumerable<AssemblyBuilderResourceAttribute> GetResourceAttributes(MemberInfo member)
        {
            return Attribute.GetCustomAttributes(member, typeof(AssemblyBuilderResourceAttribute))
                .Cast<AssemblyBuilderResourceAttribute>();
        }
        #endregion Public Methods

        #region Private Methods
        private static IEnumerable<MethodBase> GetMethodsToProcess(Type type)
        {
            return type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
        }

        private void CollectInformation(MemberInfo member)
        {
            var typeReferenceAttribute = GetReferencesAttribute(member);
            if (typeReferenceAttribute != null)
            {
                this.referenceAttributes.Add(typeReferenceAttribute);
            }

            var typeFileAttribute = GetFilesAttribute(member);
            if (typeFileAttribute != null)
            {
                this.fileAttributes.Add(typeFileAttribute);
            }

            var typeCodeAttributes = GetCodeAttributes(member);
            this.codeAttributes.AddRange(typeCodeAttributes);

            var typeResourceAttributes = GetResourceAttributes(member);
            resourceAttributes.AddRange(typeResourceAttributes);
        }
        #endregion Private Methods
    }
}